package br.com.comanda.dao.autosql.dto;

import java.util.List;

import android.database.Cursor;
import br.com.comanda.dao.autosql.GenericRepository;
import br.com.comanda.dao.autosql.RowMapper;
import br.com.comanda.dao.autosql.SQLBuilder;
import br.com.comanda.dao.autosql.dto.join.JoinBetweenMiddleTable;
import br.com.comanda.dao.autosql.dto.join.JoinBetweenTwoTables;
import br.com.comanda.dao.autosql.exception.AutoSQLException;
import br.com.comanda.util.CollectionUtils;

/**
 * Repositorio para realizar consulta com join entre duas tabelas e popular um
 * DTO. É um singleton.
 * 
 * @author thiago
 * @since 01/09/2011
 */
public final class AbstractDTORepository {

	private static volatile AbstractDTORepository instance;

	/**
	 * Classe não pode ser instanciada por objetos externos
	 */
	private AbstractDTORepository() {
		super();
	}

	public static AbstractDTORepository getInstance() {
		if (AbstractDTORepository.instance == null) {
			AbstractDTORepository.instance = new AbstractDTORepository();
		}
		return AbstractDTORepository.instance;
	}

	/**
	 * Busca uma lista do DTO
	 */
	public List<?> findListOf(Class<? extends DataTransferObject> clazz,
			String where) throws AutoSQLException {
		try {
			GenericRepository repositorio = GenericRepository
					.getInstanceFor(clazz);
			Cursor cursor = repositorio.rawQuery(getInstance().buildSQL(clazz,
					where));
			return RowMapper.fetchCursor(clazz, cursor, false);
		} catch (Exception e) {
			throw new AutoSQLException("Erro ao realizar join", e);
		}
	}

	@SuppressWarnings("unchecked")
	public Object find(Class<? extends DataTransferObject> clazz, String where)
			throws AutoSQLException {
		try {
			GenericRepository repositorio = GenericRepository
					.getInstanceFor(clazz);
			Cursor cursor = repositorio.rawQuery(getInstance().buildSQL(clazz,
					where));
			List<Object> list = (List<Object>) RowMapper.fetchCursor(clazz,
					cursor, false);
			return CollectionUtils.getFirst(list);
		} catch (Exception e) {
			throw new AutoSQLException("Erro ao realizar join", e);
		}
	}

	/**
	 * Monta o SQL com join entre duas tabelas
	 */
	public String buildSQL(Class<? extends DataTransferObject> clazz,
			String where) {
		StringBuilder select = new StringBuilder();

		if (clazz.isAnnotationPresent(JoinBetweenTwoTables.class)) {
			select.append(joinBetweenTwoTables(clazz));
		}
		if (clazz.isAnnotationPresent(JoinBetweenMiddleTable.class)) {
			select.append(joinBetweenMiddleTable(clazz));
		}

		if (where != null) {
			select.append("WHERE " + where);
		}

		return select.toString();
	}

	private String joinBetweenTwoTables(
			Class<? extends DataTransferObject> clazz) {
		StringBuilder select = new StringBuilder();

		Class<?> firstClass = firstClassOfDTO(clazz);
		String aliasFirstClass = SQLBuilder.aliasTo(firstClass);

		Class<?> joinClass = joinClassOfDTO(clazz);
		String aliasJoinClass = SQLBuilder.aliasTo(joinClass);

		select.append(String.format("SELECT %s ",
				SQLBuilder.getSelectFields(clazz)));
		select.append(String.format("FROM %s ",
				SQLBuilder.tableName(firstClass)));
		select.append(String.format("%s ", aliasFirstClass));
		select.append(String.format("INNER JOIN %s ",
				SQLBuilder.tableName(joinClass)));
		select.append(String.format("%s ", aliasJoinClass));
		select.append(String.format("ON %s ",
				SQLBuilder.joinClause(clazz, aliasFirstClass, aliasJoinClass)));

		return select.toString();
	}

	private String joinBetweenMiddleTable(
			Class<? extends DataTransferObject> clazz) {
		Class<?> middleClass = clazz
				.getAnnotation(JoinBetweenMiddleTable.class).middleTableName();
		StringBuilder select = new StringBuilder();

		Class<?> innerTableClass = firstClassOfDTO(clazz);
		String aliasInnerTable = SQLBuilder.aliasTo(innerTableClass);

		String aliasMiddleTable = SQLBuilder.aliasTo(middleClass);

		Class<?> outerTableClass = joinClassOfDTO(clazz);
		String aliasOuterTable = SQLBuilder.aliasTo(outerTableClass);

		select.append(String.format("SELECT %s ",
				SQLBuilder.getSelectFields(clazz)));
		select.append(String.format("FROM %s ",
				SQLBuilder.tableName(innerTableClass)));
		select.append(String.format("%s ", aliasInnerTable));
		select.append(String.format("INNER JOIN %s ",
				SQLBuilder.tableName(middleClass)));
		select.append(String.format("%s ", aliasMiddleTable));
		select.append(String.format("ON %s ",
				SQLBuilder.joinClause(clazz, aliasInnerTable, aliasMiddleTable)));
		select.append(String.format("INNER JOIN %s ",
				SQLBuilder.tableName(outerTableClass)));
		select.append(String.format("%s ", aliasOuterTable));
		select.append(String.format("ON %s ",
				SQLBuilder.joinClause(clazz, aliasMiddleTable, aliasOuterTable)));

		return select.toString();
	}

	/**
	 * Retorna primeira classe da consulta
	 */
	public Class<?> firstClassOfDTO(Class<? extends DataTransferObject> clazz) {
		DTO annotation = clazz.getAnnotation(DTO.class);
		return annotation.classe();
	}

	/**
	 * Retorna classe que faz o join na consulta
	 */
	public Class<?> joinClassOfDTO(Class<? extends DataTransferObject> clazz) {
		DTO annotation = clazz.getAnnotation(DTO.class);
		return annotation.joinClass();
	}
}
